package se.jocke.nb.proxy.ui;

import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JEditorPane;
import javax.swing.JTable;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultCaret;
import javax.swing.text.EditorKit;
import org.netbeans.api.settings.ConvertAsProperties;
import org.netbeans.editor.BaseDocument;
import org.netbeans.modules.editor.indent.api.Reformat;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerUtils;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Node;
import org.openide.text.CloneableEditorSupport;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.windows.TopComponent;
import org.openide.util.NbBundle.Messages;
import se.jocke.nb.proxy.monitor.model.HttpMessage;
import se.jocke.nb.proxy.monitor.service.HttpProxy;
import se.jocke.nb.proxy.monitor.service.MessageListener;
import se.jocke.nb.proxy.monitor.service.ProxySettings;
import se.jocke.nb.proxy.monitor.service.ProxySettingsManager;
import se.jocke.nb.proxy.ui.node.HttpMessageChildren;
import se.jocke.nb.proxy.ui.node.HttpMessageNode;
import se.jocke.nb.proxy.ui.node.MapTableModel;

/**
 * Top component which displays something.
 */
@ConvertAsProperties(
        dtd = "-//se.jocke.nb.proxy.ui//ProxyView//EN",
        autostore = false
)
@TopComponent.Description(
        preferredID = "ProxyViewTopComponent",
        //iconBase="SET/PATH/TO/ICON/HERE", 
        persistenceType = TopComponent.PERSISTENCE_ALWAYS
)
@TopComponent.Registration(mode = "output", openAtStartup = false)
@ActionID(category = "Window", id = "se.jocke.nb.proxy.ui.ProxyViewTopComponent")
@ActionReference(path = "Menu/Window" /*, position = 333 */)
@TopComponent.OpenActionRegistration(
        displayName = "#CTL_ProxyViewAction",
        preferredID = "ProxyViewTopComponent"
)
@Messages({
    "CTL_ProxyViewAction=ProxyView",
    "CTL_ProxyViewTopComponent=ProxyView Window",
    "HINT_ProxyViewTopComponent=This is a ProxyView window"
})
public final class ProxyMonitorTopComponent extends TopComponent implements MessageListener, ExplorerManager.Provider {

    private final transient ExplorerManager explorerManager = new ExplorerManager();

    private final HttpMessageChildren httpMessageChildren;

    private static final Set TEXT_CONTENT_TYPES = new HashSet();

    private static final Logger LOG = Logger.getLogger(ProxyMonitorTopComponent.class.getName());

    private static final Map<String, String> CONTENT_TYPE_TO_EDITOR_MAP = new HashMap<String, String>();

    static {
        CONTENT_TYPE_TO_EDITOR_MAP.put("application/atom+xml", "text/xml");
        CONTENT_TYPE_TO_EDITOR_MAP.put("application/x-www-form-urlencoded", "text/html");
        CONTENT_TYPE_TO_EDITOR_MAP.put("application/json", "text/x-json");
        CONTENT_TYPE_TO_EDITOR_MAP.put("application/octet-stream", "text/plain");
        CONTENT_TYPE_TO_EDITOR_MAP.put("application/svg+xml", "text/xml");
        CONTENT_TYPE_TO_EDITOR_MAP.put("text/html", "text/html");
        CONTENT_TYPE_TO_EDITOR_MAP.put("application/xhtml+xml", "text/html");
        CONTENT_TYPE_TO_EDITOR_MAP.put("application/xml", "text/xml");
        CONTENT_TYPE_TO_EDITOR_MAP.put("text/plain", "text/plain");
    }

    static {
        TEXT_CONTENT_TYPES.add("text/html");
        TEXT_CONTENT_TYPES.add("application/json");
        TEXT_CONTENT_TYPES.add("text/xml");
        TEXT_CONTENT_TYPES.add("text/plain");
    }

    public ProxyMonitorTopComponent() {
        initComponents();
        setName(Bundle.CTL_ProxyViewTopComponent());
        setToolTipText(Bundle.HINT_ProxyViewTopComponent());
        associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap()));
        httpMessageChildren = new HttpMessageChildren();
        explorerManager.setRootContext(new AbstractNode(httpMessageChildren));
        explorerManager.getRootContext().setDisplayName("Messages");
        explorerManager.addPropertyChangeListener(new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
                    Node[] selection = (Node[]) evt.getNewValue();
                    if (selection.length > 0) {
                        HttpMessageNode node = (HttpMessageNode) selection[0];
                        updateMessage(node.getHttpMessage());
                    }
                }
            }

        });

        responseTextPane.setDocument(new BaseDocument(false, "text/plain"));
        DefaultCaret caret1 = (DefaultCaret) requestEditorPane.getCaret();
        caret1.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
        
        DefaultCaret caret2 = (DefaultCaret) responseTextPane.getCaret();
        caret2.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);

    }

    private void updateMessage(HttpMessage httpMessage) {
        updateModel(requestHeadersTable, httpMessage.getRequestHeaders());
        updateModel(responseHeaderTable, httpMessage.getResponseHeaders());
        updateBody(httpMessage);
        updateContent(httpMessage);
        updateParameters(httpMessage.getRequestParameters());
    }

    private void updateModel(JTable table, Map<String, String> values) {
        MapTableModel model = (MapTableModel) table.getModel();
        model.update(values);
    }

    private void updateContent(HttpMessage httpMessage) {
        final String contentType = getContentType(httpMessage.getResponseHeaders());
        if (httpMessage.getContent() != null && contentType != null && TEXT_CONTENT_TYPES.contains(contentType)) {
            setEditorKit(responseTextPane, contentType);
            responseTextPane.setText(new String(httpMessage.getContent()));
            format(responseTextPane);

        } else {
            responseTextPane.setText("");
            LOG.log(Level.INFO, "Response no match for {0} content type", contentType);
        }
    }

    private void updateParameters(Map<String, List<String>> requestParameters) {
        Map<String, String> params = new HashMap<String, String>(requestParameters.size());
        Joiner joiner = Joiner.on(",").skipNulls();
        for (Map.Entry<String, List<String>> entry : requestParameters.entrySet()) {
            params.put(entry.getKey(), joiner.join(entry.getValue()));
        }
        MapTableModel model = (MapTableModel) requestParametersTable.getModel();
        model.update(params);
    }

    private void updateBody(HttpMessage httpMessage) {
        final String contentType = getContentType(httpMessage.getRequestHeaders());
        if (httpMessage.getContent() != null && contentType != null && TEXT_CONTENT_TYPES.contains(contentType)) {
            setEditorKit(requestEditorPane, contentType);
            requestEditorPane.setText(new String(httpMessage.getBody()));
            format(requestEditorPane);

        } else {
            requestEditorPane.setText("");
            LOG.log(Level.INFO, "Request no match for {0} content type", contentType);
        }

    }

    private String getContentType(Map<String, String> params) {

        String contentType = null;
        if (params.containsKey("Content-Type")) {
            contentType = params.get("Content-Type");

        } else if (params.containsKey("content-type")) {
            contentType = params.get("content-type");
        }

        if (contentType == null) {
            return null;
        
        } else {
            return Splitter.on(";").split(contentType).iterator().next();
        }

    }

    private void setEditorKit(JEditorPane editorPane, String contentType) {
        if (CONTENT_TYPE_TO_EDITOR_MAP.containsKey(contentType)) {
            EditorKit kit = CloneableEditorSupport.getEditorKit(CONTENT_TYPE_TO_EDITOR_MAP.get(contentType));
            editorPane.setEditorKit(kit);
        } else {
            LOG.log(Level.WARNING, "No editor found for {0} ", contentType);
        }
    }

    public void format(JEditorPane editorPane) {

        final BaseDocument baseDoc = (BaseDocument) editorPane.getDocument();

        final Reformat reformat = Reformat.get(baseDoc);

        reformat.lock();

        try {
            baseDoc.runAtomic(new Runnable() {
                @Override
                public void run() {
                    try {
                        reformat.reformat(0, baseDoc.getLength());
                    } catch (BadLocationException ex) {
                        Exceptions.printStackTrace(ex);
                    }
                }
            });

        } finally {
            reformat.unlock();
        }
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jSplitPane1 = new javax.swing.JSplitPane();
        jPanel1 = new javax.swing.JPanel();
        jToolBar1 = new javax.swing.JToolBar();
        deleteButton = new javax.swing.JButton();
        proxyRequestListView = new HttpMessageListView();
        responseTabbedPane = new javax.swing.JTabbedPane();
        jScrollPane3 = new javax.swing.JScrollPane();
        requestEditorPane = new javax.swing.JEditorPane();
        jScrollPane2 = new javax.swing.JScrollPane();
        requestHeadersTable = new javax.swing.JTable();
        jScrollPane1 = new javax.swing.JScrollPane();
        requestParametersTable = new javax.swing.JTable();
        jScrollPane5 = new javax.swing.JScrollPane();
        responseTextPane = new javax.swing.JEditorPane();
        jScrollPane4 = new javax.swing.JScrollPane();
        responseHeaderTable = new javax.swing.JTable();

        jPanel1.setLayout(new java.awt.BorderLayout());

        jToolBar1.setFloatable(false);
        jToolBar1.setOrientation(javax.swing.SwingConstants.VERTICAL);
        jToolBar1.setRollover(true);

        deleteButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/se/jocke/nb/proxy/ui/node/icon/delete.gif"))); // NOI18N
        org.openide.awt.Mnemonics.setLocalizedText(deleteButton, org.openide.util.NbBundle.getMessage(ProxyMonitorTopComponent.class, "ProxyMonitorTopComponent.deleteButton.text")); // NOI18N
        deleteButton.setFocusable(false);
        deleteButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        deleteButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        deleteButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                deleteButtonActionPerformed(evt);
            }
        });
        jToolBar1.add(deleteButton);

        jPanel1.add(jToolBar1, java.awt.BorderLayout.WEST);

        proxyRequestListView.setPreferredSize(new java.awt.Dimension(400, 2));
        jPanel1.add(proxyRequestListView, java.awt.BorderLayout.CENTER);

        jSplitPane1.setLeftComponent(jPanel1);

        requestEditorPane.setEditable(false);
        jScrollPane3.setViewportView(requestEditorPane);

        responseTabbedPane.addTab(org.openide.util.NbBundle.getMessage(ProxyMonitorTopComponent.class, "ProxyMonitorTopComponent.jScrollPane3.TabConstraints.tabTitle_1"), jScrollPane3); // NOI18N

        requestHeadersTable.setModel(new MapTableModel());
        jScrollPane2.setViewportView(requestHeadersTable);

        responseTabbedPane.addTab(org.openide.util.NbBundle.getMessage(ProxyMonitorTopComponent.class, "ProxyMonitorTopComponent.jScrollPane2.TabConstraints.tabTitle"), jScrollPane2); // NOI18N

        requestParametersTable.setModel(new MapTableModel());
        jScrollPane1.setViewportView(requestParametersTable);

        responseTabbedPane.addTab(org.openide.util.NbBundle.getMessage(ProxyMonitorTopComponent.class, "ProxyMonitorTopComponent.jScrollPane1.TabConstraints.tabTitle_1"), jScrollPane1); // NOI18N

        jScrollPane5.setViewportView(responseTextPane);

        responseTabbedPane.addTab(org.openide.util.NbBundle.getMessage(ProxyMonitorTopComponent.class, "ProxyMonitorTopComponent.jScrollPane5.TabConstraints.tabTitle"), jScrollPane5); // NOI18N

        responseHeaderTable.setModel(new MapTableModel());
        jScrollPane4.setViewportView(responseHeaderTable);

        responseTabbedPane.addTab(org.openide.util.NbBundle.getMessage(ProxyMonitorTopComponent.class, "ProxyMonitorTopComponent.jScrollPane4.TabConstraints.tabTitle"), jScrollPane4); // NOI18N

        jSplitPane1.setRightComponent(responseTabbedPane);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jSplitPane1, javax.swing.GroupLayout.Alignment.TRAILING)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jSplitPane1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 265, Short.MAX_VALUE)
        );
    }// </editor-fold>//GEN-END:initComponents

    private void deleteButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteButtonActionPerformed
        httpMessageChildren.clear();
        updateMessage(new HttpMessage(""));
    }//GEN-LAST:event_deleteButtonActionPerformed

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton deleteButton;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JScrollPane jScrollPane2;
    private javax.swing.JScrollPane jScrollPane3;
    private javax.swing.JScrollPane jScrollPane4;
    private javax.swing.JScrollPane jScrollPane5;
    private javax.swing.JSplitPane jSplitPane1;
    private javax.swing.JToolBar jToolBar1;
    private javax.swing.JScrollPane proxyRequestListView;
    private javax.swing.JEditorPane requestEditorPane;
    private javax.swing.JTable requestHeadersTable;
    private javax.swing.JTable requestParametersTable;
    private javax.swing.JTable responseHeaderTable;
    private javax.swing.JTabbedPane responseTabbedPane;
    private javax.swing.JEditorPane responseTextPane;
    // End of variables declaration//GEN-END:variables
    @Override
    public void componentOpened() {
        ProxySettingsManager manager = Lookup.getDefault().lookup(ProxySettingsManager.class);
        ProxySettings proxySettings = manager.getProxySettings();
        HttpProxy proxy = Lookup.getDefault().lookup(HttpProxy.class);
        proxy.registerListener(this);
        proxy.start(proxySettings.getProxyPort(), proxySettings.getProxyToHost(), proxySettings.getProxyToPort());
    }

    @Override
    public void componentClosed() {
        HttpProxy proxy = Lookup.getDefault().lookup(HttpProxy.class);
        proxy.removeListener(this);
        proxy.stop();
    }

    void writeProperties(java.util.Properties p) {
        // better to version settings since initial version as advocated at
        // http://wiki.apidesign.org/wiki/PropertyFiles
        p.setProperty("version", "1.0");
        // TODO store your settings
    }

    void readProperties(java.util.Properties p) {
        String version = p.getProperty("version");
        // TODO read your settings according to their version
    }

    @Override
    public void onMessage(final HttpMessage httpMessage) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                httpMessageChildren.add(httpMessage);
            }
        });
    }

    @Override
    public ExplorerManager getExplorerManager() {
        return explorerManager;
    }
}
